Skip to content

docs(licensing): align ThreadPlane branding + add /docs/licensing landing#532

Merged
blove merged 15 commits into
mainfrom
claude/licensing-docs-alignment
May 24, 2026
Merged

docs(licensing): align ThreadPlane branding + add /docs/licensing landing#532
blove merged 15 commits into
mainfrom
claude/licensing-docs-alignment

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 22, 2026

Summary

Editorial cleanup of every licensing-touching surface plus a new canonical /docs/licensing landing page.

Strategy decisions (from prior brainstorm)

  1. Brand: converge on ThreadPlane (and THREADPLANE_LICENSE env var) — drop "Cacheplane" naming in customer-visible copy
  2. Payment framing: "Annual license" (one-time payment, 12 months valid). Drop "subscribing".
  3. Eval period: 30 calendar days from first commercial use, good-faith — no telemetry
  4. Docs IA: single `/docs/licensing` page covering model + tier scoping + install + eval + refunds + FAQ link

Files

  • New: `apps/website/src/app/docs/licensing/page.tsx` (canonical landing)
  • Email: `apps/minting-service/src/lib/email.ts` (and email.spec.ts)
  • Brand: `COMMERCIAL.md`, `README.md`, `libs/chat/{README,NOTICE,COMMERCIAL-USE}.md`
  • Pricing: `pricing/tiers.config.ts`, `apps/website/src/app/pricing/page.tsx`, `PricingFAQ.tsx`
  • Footer: `Footer.tsx` ("Licensing" link → `/docs/licensing`)
  • Thanks page: button → `/docs/licensing`

Out of scope (deferred)

  • Legal entity question (Cacheplane d/b/a vs ThreadPlane in root `LICENSE`/`NOTICE`) — needs legal call, not editorial
  • `developer_seat` per-dev vs per-org policy decision

Test plan

  • minting-service unit tests pass (email.spec.ts updated to new "BEGIN THREADPLANE LICENSE" delimiter)
  • Website production build green
  • Visual review of `/docs/licensing` after deploy
  • Verify email rendering of next test purchase

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment May 24, 2026 4:47am

Request Review

blove and others added 14 commits May 23, 2026 21:42
…ing page

Editorial cleanup across all licensing-touching surfaces:

- New /docs/licensing landing page (linked from email + footer + /thanks)
- Email copy: "Thanks for subscribing" → "Thanks for your ThreadPlane license
  purchase"; CACHEPLANE_LICENSE → THREADPLANE_LICENSE in installation snippet;
  install snippet now shows provideChat() pattern (the actual API), not
  process.env-only
- Brand: "Threadplane" → "ThreadPlane" in customer-visible copy and library
  NOTICE/README files
- License name: always "ThreadPlane Commercial license" (capitalized)
- Tense: drop "Starting with the next published version" from COMMERCIAL.md and
  root README — the license is live
- Eval policy: precise "30 calendar days from first commercial use, good-faith,
  no telemetry" wording in libs/chat/COMMERCIAL-USE.md and PricingFAQ
- Tier scoping: standardize "Indie" name, prefix paid features with
  "ThreadPlane Commercial license", drop Enterprise "starting at $10k/year"
  display period (was inconsistent with "Custom")
- Footer "Licensing" link → /docs/licensing (was /pricing#faq)
- /thanks page button → /docs/licensing (was /docs/chat/...)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pricing grid:
- All 5 tier cards fit one row at desktop (auto-fit minmax 200px → 5-up
  at ≥1100px, gracefully wraps below)
- Tighter padding, smaller price (32px vs 40px)
- Inline price + period ($299/dev/yr)
- Per-card subtitle ("per developer", "one app · one dev")
- "MOST POPULAR" pill badge floating above the highlighted Dev Seat card,
  with a soft blue glow
- "Best for: ..." moved out of the feature bullet list into its own row,
  separated by a hairline
- "ThreadPlane Commercial license" bullet removed from every card,
  replaced by a single disclaimer below the grid
- CTAs tightened: "Buy indie license" → "Buy Indie",
  "Buy developer seat" → "Get Developer Seat",
  "Contact sales" → "Talk to Sales"
- Features tightened to 3 lines per card

Licensing FAQ:
- Now uses the shared <FAQ> component (chevron-style accordion)
- Centered heading + "Questions" eyebrow to match the home FAQ section
- Surface switched from canvas → white for visual continuity with home

Tier config (pricing/tiers.config.ts):
- Added `subtitle` and `bestFor` fields (display-only; no Stripe impact)
- Renamed "Community / Noncommercial" → "Community"
- Dropped "starting at $10k/year" from Enterprise display period

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Page-level changes:
- H1 → "Simple, transparent pricing" (drop "Pricing for production AI chat
  interfaces" and the lede paragraph below)
- Hero section uses tight vertical rhythm to pull the table closer
- Remove the "A license is required when @ngaf/chat is used in a commercial
  product..." SmallNote — redundant with the table
- Remove the redundant "Commercial evaluation is free for 30 days" note
  below the table — the table already shows a "30-day commercial eval" row
- PricingGrid component removed from page (file kept for now in case the
  card layout is wanted elsewhere)

CompareTable (rewritten to be the primary pricing display):
- Header row: tier name + MOST POPULAR pill on Dev Seat
- Sub-header row: price + period inline ($299 /dev/yr)
- Feature rows pull all data from the same set we used in the grid plus
  License, Commercial production use, SLA, Security review
- Footer row: per-column CTA button (outlined "secondary" for non-highlight
  tiers; solid "primary" for Dev Seat — more visually apparent than the
  prior ghost variant)
- Dev Seat column tinted via tokens.surfaces.surfaceTinted
- Disclaimer below table: "All paid tiers include the ThreadPlane Commercial
  license · One-time annual payment · 12-month validity"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove "What happens to older MIT versions?" from the pricing FAQ
- Remove the bottom-of-pricing "Because commercial use requires a license,
  @ngaf/chat is source-available rather than OSI open source..." footnote
- libs/chat/CHANGELOG.md: present-tense license entry; drop the historical
  MIT grandfathering line
- PricingFAQ.spec: question count drops to 6; assertion now reads the
  question span (the shared FAQ component renders a chevron next to it
  which the old textContent assertion didn't account for)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rise

Lead form (value-prop + qualifying form direction):
- Headline: "Built for procurement. / Backed by delivery."
- Subtitle: "Volume licensing, custom contract, and optional concierge
  delivery — so your first Angular agent ships, not just compiles."
- Side-by-side layout at wide widths: value props on the left, form on
  the right. Stacks vertically on narrow widths.
- Four value-prop tiles. The Pilot-to-Prod tile is highlighted with the
  accent border + tinted background, plus a deep link to /pilot-to-prod.
- Form gains qualifying fields:
    - Team size (select: 1-5 / 6-25 / 26-100 / 100+ developers)
    - Timeline (select: This quarter / Next quarter / 6+ months / exploring)
    - Pilot-to-Prod interest (radio: Yes, include it / Tell me more /
      License only) — sent as `pilot_interest` in the lead payload so
      sales can route accordingly
- Company is now required (was optional) — enterprise leads always have
  a company
- CTA: "Request enterprise quote" (was "Get in touch")

Compare table: new "Pilot-to-Prod engagement" row, marked "Optional" for
Enterprise and "—" for everything else. Makes the bridge visible without
having to scroll to the lead form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion

Licensing section:
- Drop License row (license name is implicit from the tier)
- Drop Security review (folded into Enterprise's value-prop block instead)
- Drop Dev · staging · prod (implicit for all paid tiers)
- Rename 'Commercial production use' to 'Commercial'
- Enterprise Support → 'Slack Connect' (was 'Priority + private channel')
- Pilot-to-Prod row value for Enterprise → 'Weekly 30-min check-in'
  (was 'Optional'). All non-Enterprise tiers show —

What's in the box section (new):
- Headless chat primitives
- Durable threads
- Interrupts (human-in-the-loop)
- Subagents + delegation
- Planning + memory
- Generative UI (json-render + A2UI)
- Signal-based streaming
- Citations + sources panel
- LangGraph + AG-UI adapters
- Theme presets

All tiers ✓ — Community gets noncommercial use, paid tiers get commercial.

CTAs in header AND footer:
- The Plan/Price header rows now have a third row with the CTA buttons,
  so the buyer can act without scrolling past the table
- tfoot keeps its CTA row so long scrolls still have an action close at hand

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tier ladder is now: Community · Developer Seat · Team · Enterprise.

Why:
- Per-app pricing ("App Deployment") was confusing — what counts as an
  app, a domain, a URL? — and hard to enforce. Dropped entirely.
- Indie was a fence-sit between "free for solo OSS" and "Developer Seat"
  — Developer Seat covers solo developers too, so Indie is redundant.
- Team is the new bulk/procurement-friendly SKU: 5 developer seats at a
  flat $1,495/yr (= 5 × $299, no discount, no premium). The value is
  single SKU + single renewal + procurement convenience.

Comparison table:
- Drop "Commercial apps" row (no per-app pricing means the column was
  meaningless)
- Rename "Commercial production use" → "Commercial"
- Drop "License" + "Security review" + "Dev · staging · prod" rows
- Enterprise Support → "Slack Connect"
- Add "Pilot-to-Prod" row → Enterprise = "Weekly 30-min check-in"
- New "What's in the box" section listing core library capabilities
- CTAs in BOTH header and footer of the table

Enterprise pricing:
- Display "From $4,000/mo" (was "Custom annual"). Sales-led; 8-week
  Pilot-to-Prod engagement with weekly 30-min check-ins is included.

Backend changes:
- libs/licensing LicenseTier = 'developer_seat' | 'team' | 'enterprise'
  (dropped indie + app_deployment from the union)
- apps/minting-service MintableTier = 'developer_seat' | 'team';
  computeSeats('team', _) returns 5
- apps/website checkout/session route BUYABLE_SLUGS = { developer_seat,
  team }
- Analytics events: pricing_tier_indie, pricing_tier_app_deployment
  replaced by pricing_tier_team
- pricing/tiers.generated.ts: indie + app_deployment price IDs removed.
  Team price ID needs sync-products.ts run before /pricing checkout will
  succeed for Team — falls back to a 503 with a helpful message until
  then. Developer Seat price ID still valid.
- /docs/licensing tier scoping table updated (3 rows: Dev Seat / Team /
  Enterprise)
- PricingGrid.tsx deleted (unused after the table-as-pricing pivot)

Test updates:
- minting-service tier.spec, handlers.spec, email.spec, sign.spec,
  route.spec all updated to use developer_seat/team fixtures
- libs/licensing sign-license.spec updated

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…As below each

- Remove "End users" row (was Unlimited on every tier — no signal)
- Community Support: "Community" → "GitHub"
- Developer Seat Support: "Email" → "GitHub" (matches Community —
  paid email support starts at Team)
- Split the single comparison table into TWO stacked tables:
  - Section 1: "Licensing" header, columns include price row, rows
    cover Commercial / Developers / 30-day eval / Support / SLA /
    Pilot-to-Prod
  - Section 2: "What's in the box" header, no price row repeated,
    rows cover all ✓ library capabilities
- CTAs now sit BELOW each section as a separate CSS grid, aligned with
  the table columns but visually outside the table boundary. 24px gap
  between table and CTA strip; 56px gap between the two sections.
- Drop the in-header CTA row and the tfoot CTA row from the prior
  single-table layout — replaced by the strip pattern.
- Drop the "One-time annual payment · 12-month validity" disclaimer
  while subscription pricing is being planned (will return with new
  copy after the Stripe refactor).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pricing model is now recurring subscriptions, with both monthly and annual
cycles. Annual saves ~15% vs 12× monthly (Developer Seat: $29/mo or
$299/yr; Team: $149/mo or $1,495/yr). Enterprise is unchanged (from
$4,000/mo, sales-led).

This PR covers the WEBSITE-VISIBLE half of the migration. A follow-up PR
will refactor the minting service from `checkout.session.completed` to
subscription events (created/updated/deleted, invoice.paid), wire token
expiry to current_period_end, and implement the grace-until-period-end
cancellation behaviour.

Schema (pricing/tiers.config.ts):
- TierPrice = { cents, display, period }, one per cycle
- TierConfig.prices: Record<BillingCycle, TierPrice>
- Removed displayPrice/displayPeriod/priceCents (replaced by .prices)
- Helpers: annualSavingsDollars(tier), annualDiscountPercent()

UI (apps/website/src/components/pricing/CompareTable.tsx):
- Monthly | Annual toggle pill above the table; Annual is default
- Toggle label dynamically shows "save N%" pulled from Team's discount
- Toggling flips the Price row across all paid columns; Community + Enterprise
  stay constant
- CtaStrip receives cycle and includes <input name="billing_cycle"> in the
  Stripe POST so the server picks the right price

Checkout route (apps/website/src/app/api/checkout/session/route.ts):
- mode: 'payment' → 'subscription'
- Accepts `billing_cycle` (default 'annual')
- STRIPE_PRICE_IDS shape changes to { tier: { monthly, annual } }
- subscription_data.metadata carries ngaf_tier_slug + ngaf_billing_cycle
  forward so the (future) minting service can route on either field
- Returns 503 with a helpful message if the price ID for the requested
  (tier, cycle) hasn't been synced yet

Stripe sync (scripts/stripe/sync-products.ts):
- Creates BOTH monthly and annual recurring prices per buyable tier
- Idempotent: matches by (interval, unit_amount); archives stale prices
- Generated file shape updated to the nested { monthly, annual } map

Docs (apps/website/src/app/docs/licensing/page.tsx):
- Tier table shows both monthly and annual prices
- Disclaimer rewritten: "Paid tiers are recurring subscriptions. Annual
  saves ~15% vs monthly. Cancel anytime — the license stays valid through
  the end of the current paid period."

Tests (route.spec.ts):
- Updated STRIPE_PRICE_IDS mock to the nested shape
- New assertions: default cycle is annual; billing_cycle=monthly routes to
  the monthly price; invalid billing_cycle returns 400; Team routes to the
  team annual price by default; subscription mode is used

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /angular landing page actually documents the @ngaf/langgraph adapter
library (eyebrow already says "@ngaf/langgraph"), so the URL was misleading.

- apps/website/src/app/angular/ → apps/website/src/app/langgraph/
- page.tsx metadata pathname → '/langgraph'
- solutions/[slug] LIBRARY_HREF.Agent → '/langgraph'
- Footer Libraries column "Angular" link → "LangGraph" pointing at /langgraph

Untouched (intentional):
- GitHub repo URL cacheplane/angular-agent-framework — repo name, not a route
- EcosystemStrip "Angular" entry — about Angular the framework
- WhitePaperBlock paper="angular" id — references the streaming whitepaper
- Eyebrow tone="angular" / Pill variant="angular" — design tokens

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…vents

The website checkout now opens Stripe sessions in `mode: 'subscription'`,
so the minting service has to follow suit. This drops the one-shot
`checkout.session.completed` flow and wires the handler to the
subscription lifecycle.

Schema
- Rename `licenses.stripe_payment_id` → `licenses.stripe_subscription_id`
  (migration 0002, snapshot + journal updated).
- Add `getLicensesByCustomerId` query for customer-keyed lookups.

Handlers (apps/minting-service/src/lib/handlers.ts)
- `customer.subscription.created`: mint a license, expiresAt set to
  `subscription.current_period_end`, email the token.
- `customer.subscription.updated`: re-mint only when seat count or
  period_end actually change. Status transitions to canceled/unpaid do
  NOT immediately revoke — the license expires naturally at
  current_period_end.
- `invoice.paid` (`billing_reason: subscription_cycle`): renewal — fetch
  the subscription, re-mint with the new period_end, email new token.
  The first-time `subscription_create` invoice is skipped (handled by
  `customer.subscription.created`).
- `charge.refunded`: keep revoking immediately, but key off
  `charge.customer` since we no longer track payment_intent → license.
  Revoke every active license for that customer.

Tier metadata is read from `subscription.metadata.ngaf_tier_slug` first,
with the price metadata as a fallback. Seats default to
`items[0].quantity` (the `team` tier still overrides to 5 via
`computeSeats`).

Tests
- handlers.spec.ts rewritten with subscription/invoice fixtures.
- licenses.spec.ts uses `sub_*` IDs and `stripeSubscriptionId`.
- remint.spec.ts already on the new naming.

Follow-up (out-of-band, not in this PR)
- Update the production Stripe webhook endpoint
  `we_1TZcsHGYRsLErhxbdN2JTFTr` `enabled_events`:
  add `customer.subscription.created`,
  `customer.subscription.updated`,
  `customer.subscription.deleted`,
  `invoice.paid`; keep `charge.refunded`; drop
  `checkout.session.completed`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New landing page for @ngaf/ag-ui that lives alongside /langgraph.

Positioning:
- Hero: "One adapter. Eight backends." — leads with the multi-backend
  differentiation (LangGraph / CrewAI / Mastra / MS Agent Framework / AG2
  / Pydantic AI / AWS Strands / CopilotKit)
- Light decision callout under the pills points LangGraph-on-LangGraph
  visitors to @ngaf/langgraph for the native path
- FeatureBlock 1 visual: BackendsGrid listing the 8 runtimes
- FeatureBlock 2 visual: BrowserFrame with provideAgUiAgent app.config.ts
- Mirrors /langgraph structure (Hero → FeatureBlock × 2 → FinalCTA)

Footer:
- New AG-UI link in the Libraries column under LangGraph
- footer_ag_ui added to CtaId union

Audit cleanup folded in:
- AngularPage → LangGraphPage function rename
- AngularCodeShowcase → LangGraphCodeShowcase (dir + file + export)
- Footer "Getting Started" (duplicate of Documentation) replaced with
  "Pilot to Prod" to surface that page in the footer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nggraph rename

- CompareTable.tsx: explicitly type `variant: 'primary' | 'secondary'` so the
  ternary doesn't widen to `string` (which fails Button's variant prop type)
- e2e/website.spec.ts: replace /angular with /langgraph in canonical-URL
  and whitepaper-link expectations; also add /ag-ui to the canonical sweep

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ructure

- 'plan cards' test asserts new tier names (Community / Developer Seat /
  Team / Enterprise) instead of the dropped "Open Source" label
- 'lead form validates required fields' now scopes to #lead-form to avoid
  triggering the comparison table's Stripe checkout buttons (which are
  also type=submit)
- 'lead form posts to /api/leads' uses the new "Request enterprise quote"
  CTA label (was "Get in touch") and scopes locator queries to the lead
  form so they don't collide with other form inputs on the page

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blove blove force-pushed the claude/licensing-docs-alignment branch from c68752b to 3982ba2 Compare May 24, 2026 04:45
@blove blove merged commit 5066fad into main May 24, 2026
48 checks passed
blove added a commit that referenced this pull request May 25, 2026
Ran scripts/stripe/sync-products.ts against the live Stripe test-mode
account after PR #532 landed. The script created 4 recurring prices
(monthly + annual × developer_seat + team) and archived the prior
one-time-payment prices.

Operational follow-ups completed in parallel:
- DB migration 0002 applied to the minting Neon DB
  (stripe_payment_id → stripe_subscription_id)
- Webhook endpoint we_1TZcsHGYRsLErhxbdN2JTFTr enabled_events updated:
  customer.subscription.{created,updated,deleted}, invoice.paid,
  charge.refunded (dropped checkout.session.completed)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blove added a commit that referenced this pull request May 25, 2026
* chore(stripe): regenerate price IDs after subscription sync

Ran scripts/stripe/sync-products.ts against the live Stripe test-mode
account after PR #532 landed. The script created 4 recurring prices
(monthly + annual × developer_seat + team) and archived the prior
one-time-payment prices.

Operational follow-ups completed in parallel:
- DB migration 0002 applied to the minting Neon DB
  (stripe_payment_id → stripe_subscription_id)
- Webhook endpoint we_1TZcsHGYRsLErhxbdN2JTFTr enabled_events updated:
  customer.subscription.{created,updated,deleted}, invoice.paid,
  charge.refunded (dropped checkout.session.completed)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(minting): read current_period_end from subscription item, not the sub object

Stripe API version 2026-04-22 (dahlia) moved `current_period_end` and
`current_period_start` from the Subscription object to each SubscriptionItem.
The handler was reading the legacy subscription-level field, which is now
always null, causing `handleSubscriptionCreated` to throw with
`subscription <id> has no current_period_end` for every new subscription.

Read the item-level field first, fall back to the legacy subscription-level
field for replayed historical events or older API versions.

Verified with a live test-mode Team annual subscription
(sub_1Tapk3GYRsLErhxb3jnWnxPy): handler now mints + emails the license with
expires_at = 2027-05-25 (matches Stripe's item.current_period_end).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant